#include "time_util.hh"
#include "object_pool.hpp"
#include "event/event_queue.hh"
#include "event/event.hh"
#include <sstream>
#include <iomanip>

#if (!defined(WIN32) && !defined(_WIN64))
#include <sys/time.h>
#include <cstring>
#include <cstdlib>
#endif

#if (defined(WIN32) || defined(_WIN64))
#include <Windows.h>

struct timezone {
    int  tz_minuteswest; /* minutes W of Greenwich */
    int  tz_dsttime;     /* type of dst correction */
};
int gettimeofday(struct timeval* p, struct timezone *z) {
    if (p) {
        time_t clock;
        struct tm tm;
        SYSTEMTIME st;
        GetLocalTime(&st);
        tm.tm_year = st.wYear - 1900;
        tm.tm_mon = st.wMonth - 1;
        tm.tm_mday = st.wDay;
        tm.tm_hour = st.wHour;
        tm.tm_min = st.wMinute;
        tm.tm_sec = st.wSecond;
        tm.tm_isdst = -1;
        clock = mktime(&tm);
        p->tv_sec = (long)clock;
        p->tv_usec = st.wMilliseconds * 1000;
    }
    if (z) {
        TIME_ZONE_INFORMATION TimeZoneInformation;
        DWORD tzi;
        if ((tzi = GetTimeZoneInformation(&TimeZoneInformation)) != TIME_ZONE_ID_INVALID) {
            z->tz_minuteswest = TimeZoneInformation.Bias;
            if (tzi == TIME_ZONE_ID_DAYLIGHT) {
                z->tz_dsttime = 1;
            } else {
                z->tz_dsttime = 0;
            }
        } else {
            z->tz_minuteswest = 0;
            z->tz_dsttime = 0;
        }
    }
    return 0;
}

#endif /* defined(WIN32) || defined(_WIN64) */

namespace kratos { namespace corelib {

static time_t millionSecondsTimestamp_ = 0;
static time_t secondsTimestamp_ = 0;
static Datetime daytimeUTC_;
static Datetime daytimeTZ_;
static tm tmUTC_;
static tm tmTZ_;
static EventQueue* timeEventQueue_ = nullptr;
static int timezone_ = 86401;
constexpr static Weekday Weekdays[] = { Weekday::Sunday, Weekday::Monday, Weekday::Tuesday, Weekday::Wednesday, Weekday::Thursday, Weekday::Friday, Weekday::Saturday };

static int minutePrev_ = 0;
static int hourPrev_ = 0;
static int dayPrev_ = 0;
static int monthPrev_ = 0;
static int yearPrev_ = 0;
static int weekdayPrev_ = 0;

typedef std::chrono::duration<int, std::ratio<3600 * 24> > days;

void gmtime_os(struct tm* tm, time_t now) {
#if (defined(WIN32) || defined(_WIN64))
    gmtime_s(tm, &now);
#else
    gmtime_r(&now, tm);
#endif // (defined(WIN32) || defined(_WIN64))
}

void localtime_os(struct tm* tm, time_t now) {
#if (defined(WIN32) || defined(_WIN64))
    localtime_s(tm, &now);
#else
    localtime_r(&now, tm);
#endif // (defined(WIN32) || defined(_WIN64))
}

int gettimezone() {
    if (timezone_ != 86401) {
        return timezone_;
    }
    struct timezone tz;
    timeval* nullp = nullptr;
    gettimeofday(nullp, &tz);
    timezone_ = tz.tz_minuteswest * 60;
    return timezone_;
}

void saveUTC(const std::chrono::system_clock::time_point& now, std::time_t timet) {
    gmtime_os(&tmUTC_, timet);
    daytimeUTC_ = { tmUTC_.tm_year + 1900, tmUTC_.tm_mon + 1, tmUTC_.tm_mday, tmUTC_.tm_hour, tmUTC_.tm_min, tmUTC_.tm_sec };
    daytimeUTC_.wday = Weekdays[tmUTC_.tm_wday];
}

void saveTZ(const std::chrono::system_clock::time_point& now, std::time_t timet) {
    localtime_os(&tmTZ_, timet);
    daytimeTZ_ = { tmTZ_.tm_year + 1900, tmTZ_.tm_mon + 1, tmTZ_.tm_mday, tmTZ_.tm_hour, tmTZ_.tm_min, tmTZ_.tm_sec };
    daytimeTZ_.wday = Weekdays[tmTZ_.tm_wday];
}

void saveTimestamp(const std::chrono::system_clock::time_point& now, std::time_t timet) {
    auto millionSecondsPoint = std::chrono::time_point_cast<std::chrono::milliseconds>(now);
    auto millionSecondsDuration = std::chrono::duration_cast<std::chrono::milliseconds>(millionSecondsPoint.time_since_epoch());
    millionSecondsTimestamp_ = millionSecondsDuration.count();
    secondsTimestamp_ = timet;
}

void checkEvent() {
    if (!minutePrev_) {
        minutePrev_ = daytimeTZ_.minute;
    }
    if (!hourPrev_) {
        hourPrev_ = daytimeTZ_.hour;
    }
    if (!dayPrev_) {
        dayPrev_ = daytimeTZ_.day;
    }
    if (!weekdayPrev_) {
        weekdayPrev_ = tmTZ_.tm_wday;
    }
    if (!monthPrev_) {
        monthPrev_ = daytimeTZ_.month;
    }
    if (!yearPrev_) {
        yearPrev_ = daytimeTZ_.year;
    }
    if (minutePrev_ != daytimeTZ_.minute) {
        minutePrev_ = daytimeTZ_.minute;
        TimeUtil::getTimeEventQueue()->fireEventNow<EventTime>(static_cast<EventID>(ReserveEvent::EVENT_ID_TIME_MINUTE_CHANGE));
    }
    if (hourPrev_ != daytimeTZ_.hour) {
        hourPrev_ = daytimeTZ_.hour;
        TimeUtil::getTimeEventQueue()->fireEventNow<EventTime>(static_cast<EventID>(ReserveEvent::EVENT_ID_TIME_HOUR_CHANGE));
    }
    if (dayPrev_ != daytimeTZ_.day) {
        dayPrev_ = daytimeTZ_.day;
        TimeUtil::getTimeEventQueue()->fireEventNow<EventTime>(static_cast<EventID>(ReserveEvent::EVENT_ID_TIME_DAY_CHANGE));
    }
    if ((weekdayPrev_ == 0) && (tmTZ_.tm_wday == 1)) {
        weekdayPrev_ = tmTZ_.tm_wday;
        TimeUtil::getTimeEventQueue()->fireEventNow<EventTime>(static_cast<EventID>(ReserveEvent::EVENT_ID_TIME_WEEK_CHANGE));
    }
    if (monthPrev_ != daytimeTZ_.month) {
        monthPrev_ = daytimeTZ_.month;
        TimeUtil::getTimeEventQueue()->fireEventNow<EventTime>(static_cast<EventID>(ReserveEvent::EVENT_ID_TIME_MONTH_CHANGE));
    }
    if (yearPrev_ != daytimeTZ_.year) {
        yearPrev_ = daytimeTZ_.year;
        TimeUtil::getTimeEventQueue()->fireEventNow<EventTime>(static_cast<EventID>(ReserveEvent::EVENT_ID_TIME_YEAR_CHANGE));
    }
}

void TimeUtil::update() {
    auto timet = time(nullptr);
    auto now = std::chrono::system_clock::from_time_t(timet);
    saveUTC(now, timet);
    saveTZ(now, timet);
    saveTimestamp(now, timet);
    checkEvent();
}

void TimeUtil::update(std::time_t current) {    
    auto timet = current / 1000;
    auto now = std::chrono::system_clock::from_time_t(timet);
    saveUTC(now, timet);
    saveTZ(now, timet);
    saveTimestamp(now, timet);
    checkEvent();
}

std::time_t TimeUtil::getNanoSecondsTimestampOS() {
    auto now = std::chrono::system_clock::now();
    auto nanoSecondsDuration = std::chrono::duration_cast<std::chrono::nanoseconds>(now.time_since_epoch());
    return nanoSecondsDuration.count();
}

std::time_t TimeUtil::getMillionSecondsTimestamp() {
    return millionSecondsTimestamp_;
}

std::time_t TimeUtil::getMillionSecondsTimestampOS() {
    auto now = std::chrono::system_clock::now();
    auto millionSecondsDuration = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch());
    return millionSecondsDuration.count();
}

std::time_t TimeUtil::getSecondsTimestamp() {
    return secondsTimestamp_;
}

std::time_t TimeUtil::getSecondsTimestampOS() {
    return time(nullptr);
}

const Datetime& TimeUtil::UTC::getDatetimeUTC() {
    return daytimeUTC_;
}

const Datetime TimeUtil::UTC::getDatetimeUTC(std::time_t t) {
    struct tm tms;
    memset(&tms, 0, sizeof(tms));
    gmtime_os(&tms, t);
    Datetime dt = { tms.tm_year + 1900, tms.tm_mon + 1, tms.tm_mday, tms.tm_hour, tms.tm_min, tms.tm_sec };
    dt.wday = Weekdays[tms.tm_wday];
    return dt;
}

const Datetime& TimeUtil::CIVIL::getDatetime() {
    return daytimeTZ_;
}

const Datetime TimeUtil::CIVIL::getDatetime(std::time_t t) {
    struct tm tms;
    memset(&tms, 0, sizeof(tms));
    localtime_os(&tms, t);
    Datetime dt = { tms.tm_year + 1900, tms.tm_mon + 1, tms.tm_mday, tms.tm_hour, tms.tm_min, tms.tm_sec };
    dt.wday = Weekdays[tms.tm_wday];
    return dt;
}

const tm * TimeUtil::UTC::get_tmUTC() {
    return &tmUTC_;
}

const tm* TimeUtil::CIVIL::get_tm() {
    return &tmTZ_;
}

int TimeUtil::CIVIL::getDays(std::time_t t) {
    struct tm tms;
    memset(&tms, 0, sizeof(tms));
    localtime_os(&tms, t);
    auto tday = days_from_civil<int>(tms.tm_year + 1900, tms.tm_mon + 1, tms.tm_mday);
    auto nowDay = days_from_civil<int>(daytimeTZ_.year, daytimeTZ_.month, daytimeTZ_.day);
    return nowDay - tday;
}

int TimeUtil::CIVIL::getDays(std::time_t t1, std::time_t t2) {
    struct tm tm1;
    struct tm tm2;
    memset(&tm1, 0, sizeof(tm1));
    memset(&tm2, 0, sizeof(tm2));
    localtime_os(&tm1, t1);
    localtime_os(&tm2, t2);
    auto t1day = days_from_civil<int>(tm1.tm_year + 1900, tm1.tm_mon + 1, tm1.tm_mday);
    auto t2day = days_from_civil<int>(tm2.tm_year + 1900, tm2.tm_mon + 1, tm2.tm_mday);
    return t2day - t1day;
}

int TimeUtil::UTC::getDaysUTC(std::time_t t) {
    struct tm tms;
    memset(&tms, 0, sizeof(tms));
    gmtime_os(&tms, t);
    auto tday = days_from_civil<int>(tms.tm_year + 1900, tms.tm_mon + 1, tms.tm_mday);
    auto nowDay = days_from_civil<int>(daytimeUTC_.year, daytimeUTC_.month, daytimeUTC_.day);
    return nowDay - tday;
}

int TimeUtil::UTC::getDaysUTC(std::time_t t1, std::time_t t2) {
    struct tm tm1;
    struct tm tm2;
    memset(&tm1, 0, sizeof(tm1));
    memset(&tm2, 0, sizeof(tm2));
    gmtime_os(&tm1, t1);
    gmtime_os(&tm2, t2);
    auto t1day = days_from_civil<int>(tm1.tm_year + 1900, tm1.tm_mon + 1, tm1.tm_mday);
    auto t2day = days_from_civil<int>(tm2.tm_year + 1900, tm2.tm_mon + 1, tm2.tm_mday);
    return t2day - t1day;
}

std::time_t TimeUtil::UTC::getTimestampFromDatetimeUTC(const Datetime & datetime) {
    return CIVIL::getTimestampFromDatetime(datetime) - (time_t)gettimezone();
}

std::time_t TimeUtil::getUTCDifference() {
    return gettimezone();
}

std::time_t TimeUtil::CIVIL::getTimestampFromDatetime(const Datetime & datetime) {
    struct tm tm1;
    memset(&tm1, 0, sizeof(tm1));
    tm1.tm_year = datetime.year - 1900;
    tm1.tm_mon = datetime.month - 1;
    tm1.tm_mday = datetime.day;
    tm1.tm_hour = datetime.hour;
    tm1.tm_min = datetime.minute;
    tm1.tm_sec = datetime.second;
    return mktime(&tm1);
}

bool TimeUtil::CIVIL::inSameDay(std::time_t t1, std::time_t t2) {
    return (0 == getDays(t1, t2));
}

bool TimeUtil::CIVIL::inSameDay(std::time_t t) {
    return inSameDay(t, secondsTimestamp_);
}

bool TimeUtil::CIVIL::inSameWeek(std::time_t t1, std::time_t t2) {
    if (t1 > t2) {
        auto t = t2;
        t2 = t1;
        t1 = t;
    }
    struct tm tm1;
    struct tm tm2;
    memset(&tm1, 0, sizeof(tm1));
    memset(&tm2, 0, sizeof(tm2));
    localtime_os(&tm1, t1);
    localtime_os(&tm2, t2);
    if ((tm1.tm_year != tm2.tm_year) || (tm1.tm_mon != tm2.tm_mon)) {
        return false;
    }
    int yday_diff = tm2.tm_yday - tm1.tm_yday;
    if (yday_diff > 7) {
        return false;
    }
    int wday1 = tm1.tm_wday ? tm1.tm_wday : 7;
    int wday2 = tm2.tm_wday ? tm2.tm_wday : 7;
    if (wday1 > wday2) {
        return false;
    }
    return true;
}

bool TimeUtil::CIVIL::inSameWeek(std::time_t t) {
    return inSameWeek(t, secondsTimestamp_);
}

bool TimeUtil::CIVIL::inSameMonth(std::time_t t1, std::time_t t2) {
    struct tm tm1;
    struct tm tm2;
    memset(&tm1, 0, sizeof(tm1));
    memset(&tm2, 0, sizeof(tm2));
    localtime_os(&tm1, t1);
    localtime_os(&tm2, t2);
    if ((tm1.tm_year != tm2.tm_year) || (tm1.tm_mon != tm2.tm_mon)) {
        return false;
    }
    return true;
}

bool TimeUtil::CIVIL::inSameMonth(std::time_t t) {
    return inSameMonth(t, secondsTimestamp_);
}

bool TimeUtil::UTC::inSameDayUTC(std::time_t t1, std::time_t t2) {
    return (0 == getDaysUTC(t1, t2));
}

bool TimeUtil::UTC::inSameDayUTC(std::time_t t) {
    return inSameDayUTC(t, secondsTimestamp_);
}

bool TimeUtil::UTC::inSameWeekUTC(std::time_t t1, std::time_t t2) {
    if (t1 > t2) {
        auto t = t2;
        t2 = t1;
        t1 = t;
    }
    struct tm tm1;
    struct tm tm2;
    memset(&tm1, 0, sizeof(tm1));
    memset(&tm2, 0, sizeof(tm2));
    gmtime_os(&tm1, t1);
    gmtime_os(&tm2, t2);
    if ((tm1.tm_year != tm2.tm_year) || (tm1.tm_mon != tm2.tm_mon)) {
        return false;
    }
    int yday_diff = tm2.tm_yday - tm1.tm_yday;
    if (yday_diff > 7) {
        return false;
    }
    int wday1 = tm1.tm_wday ? tm1.tm_wday : 7;
    int wday2 = tm2.tm_wday ? tm2.tm_wday : 7;
    if (wday1 > wday2) {
        return false;
    }
    return true;
}

bool TimeUtil::UTC::inSameWeekUTC(std::time_t t) {
    return inSameWeekUTC(t, secondsTimestamp_);
}

bool TimeUtil::UTC::inSameMonthUTC(std::time_t t1, std::time_t t2) {
    struct tm tm1;
    struct tm tm2;
    memset(&tm1, 0, sizeof(tm1));
    memset(&tm2, 0, sizeof(tm2));
    gmtime_os(&tm1, t1);
    gmtime_os(&tm2, t2);
    if ((tm1.tm_year != tm2.tm_year) || (tm1.tm_mon != tm2.tm_mon)) {
        return false;
    }
    return true;
}

bool TimeUtil::UTC::inSameMonthUTC(std::time_t t) {
    return inSameMonthUTC(t, secondsTimestamp_);
}

EventQueue * TimeUtil::getTimeEventQueue() {
    if (!timeEventQueue_) {
        timeEventQueue_ = EventQueue::getEventQueue("time");
    }
    return timeEventQueue_;
}

Datetime::Datetime()
    : year(0), month(0), day(0), hour(0), minute(0), second(0) {
}

Datetime::Datetime(std::time_t t) {
    struct tm tm1;
    memset(&tm1, 0, sizeof(tm1));
    localtime_os(&tm1, t);
    year = tm1.tm_year + 1900;
    month = tm1.tm_mon + 1;
    day = tm1.tm_mday;
    hour = tm1.tm_hour;
    minute = tm1.tm_min;
    second = tm1.tm_sec;
    wday = Weekdays[tm1.tm_wday];
}

Datetime::Datetime(const std::string & fmt) {
    int yy, mm, dd, h, m, s;
    sscanf(fmt.c_str(), "%d/%d/%d %d:%d:%d", &yy, &mm, &dd, &h, &m, &s);
    struct tm tm1;
    memset(&tm1, 0, sizeof(tm1));
    tm1.tm_year = yy - 1900;
    tm1.tm_mon = mm - 1;
    tm1.tm_mday = dd;
    tm1.tm_hour = h;
    tm1.tm_min = m;
    tm1.tm_sec = s;
    localtime_os(&tm1, mktime(&tm1));
    year = tm1.tm_year + 1900;
    month = tm1.tm_mon + 1;
    day = tm1.tm_mday;
    hour = tm1.tm_hour;
    minute = tm1.tm_min;
    second = tm1.tm_sec;
    wday = Weekdays[tm1.tm_wday];
}

Datetime::Datetime(const std::initializer_list<int>& params)
    : year(0), month(0), day(0), hour(0), minute(0), second(0) {
    auto it = params.begin();
    if (it != params.end()) {
        year = *it++;
    }
    if (it != params.end()) {
        month = *it++;
    }
    if (it != params.end()) {
        day = *it++;
    }
    if (it != params.end()) {
        hour = *it++;
    }
    if (it != params.end()) {
        minute = *it++;
    }
    if (it != params.end()) {
        second = *it++;
    }
}

std::time_t Datetime::getTimestamp() {
    return TimeUtil::CIVIL::getTimestampFromDatetime(*this);
}

std::time_t Datetime::getTimestampUTC() {
    return TimeUtil::UTC::getTimestampFromDatetimeUTC(*this);
}

std::string Datetime::getDateString() {
    std::stringstream s;
    s << year << "/" << std::setfill('0') << std::setw(2) << month << "/" << std::setfill('0') << std::setw(2) << day
        << " " << std::setfill('0') << std::setw(2) << hour << ":" << std::setfill('0') << std::setw(2) << minute << ":"
        << std::setfill('0') << std::setw(2) << second;
    return s.str();
}

}}
